<?php

namespace app\bll;

use Exception;
use hulang\tool\FileHelper;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use support\Db;
use Workerman\Events\Ev;

/**
 * 计划任务
 */
class CrontabTask
{

    /**
     * 根据任务ID查找计划任务信息
     *
     * @param int $id 计划任务的ID
     * @return array 包含计划任务信息的关联数组，如果未找到则返回空数组
     */
    public function find(int $id): array
    {
        // 在 'frame_crontab_task' 表中根据任务ID查找第一条记录
        $data = Db::table('frame_crontab_task')
            ->where('id', '=', $id)
            ->first();
        // 如果未找到记录，返回空数组；否则将对象转换为关联数组返回
        return $data === null ? [] : get_object_vars($data);
    }


    /**
     * 获取所有计划任务
     *
     * @return array
     */
    public function select(int $status): array
    {
        return Db::table('frame_crontab_task')
            ->where('status', '=', $status)
            ->get()
            ->map(function ($value) {
                return (array)$value;
            })
            ->toArray();
    }

    /**
     * 创建一条计划任务
     *
     * @param array $data
     * @return integer
     */
    public function add(array $data): int
    {
        switch ($data['tasktype']) {
            case 1:
                //事件
                if (!isset($data['event_method_id']) || $data['event_method_id'] === '') {
                    throw new Exception('事件方法不能为空');
                }
                list($eventid, $methodid) = explode('_', $data['event_method_id']);

                $bllComponentEventMethod  = new ComponentEventMethod();
                $eventAndListInfo         = $bllComponentEventMethod->findInfoAndEventById($methodid);
                $data['event']            = $eventAndListInfo['eventinfo']['ident'] . '/' . $eventAndListInfo['eventmethodinfo']['path'];

                break;
            case 2:
                //自定义方法
                if (!isset($data['custommethod']) || $data['custommethod'] === '') {
                    throw new Exception('自定义方法不能为空');
                }
                $data['event']            = $data['custommethod'];

                break;
        }

        unset($data['event_method_id']);
        unset($data['custommethod']);

        $data['rule']             = $this->createRule($data);
        try {
            $id = Db::table('frame_crontab_task')
                ->insertGetId($data);
            $this->unLockTaskFile();
            return $id;
        } catch (Exception $ex) {
            throw $ex;
        }
    }

    /**
     * 编辑计划任务
     *
     * @param int $id
     * @param array $data
     * @return boolean
     */
    public function edit(int $id, array $data): bool
    {

        switch ($data['tasktype']) {
            case 1:
                //事件
                if (!isset($data['event_method_id']) || $data['event_method_id'] === '') {
                    throw new Exception('事件方法不能为空');
                }
                list($eventid, $methodid) = explode('_', $data['event_method_id']);

                $bllComponentEventMethod  = new ComponentEventMethod();
                $eventAndListInfo         = $bllComponentEventMethod->findInfoAndEventById($methodid);
                $data['event']            = $eventAndListInfo['eventinfo']['ident'] . '/' . $eventAndListInfo['eventmethodinfo']['path'];

                break;
            case 2:
                //自定义方法
                if (!isset($data['custommethod']) || $data['custommethod'] === '') {
                    throw new Exception('自定义方法不能为空');
                }
                $data['event']            = $data['custommethod'];

                break;
        }

        unset($data['event_method_id']);
        unset($data['custommethod']);

        $data['rule']             = $this->createRule($data);
        try {
            $id = Db::table('frame_crontab_task')
                ->where('id', '=', $id)
                ->update($data);

            $this->unLockTaskFile();
            return $id;
        } catch (Exception $ex) {
            throw $ex;
        }
    }

    /**
     * 删除计划任务
     *
     * @param int $id
     * @return boolean
     */
    public function delete(int $id): bool
    {
        try {
            $id = Db::table('frame_crontab_task')
                ->where('id', '=', $id)
                ->delete();

            $this->unLockTaskFile();
            return $id;
        } catch (Exception $ex) {
            throw $ex;
        }
    }
    /**
     * 设置计划任务状态
     *
     * @param int $id
     * @return boolean
     */
    public function setStatus(int $id, int $status): bool
    {
        try {
            $id = Db::table('frame_crontab_task')
                ->where('id', '=', $id)
                ->update([
                    'status' => $status
                ]);

            $this->unLockTaskFile();
            return $id;
        } catch (Exception $ex) {
            throw $ex;
        }
    }


    /**
     * 根据传入的数据创建定时任务规则
     *
     * @param array $data 包含定时任务配置信息的数组，可能包含 'cycle', 'minute', 'hour', 'time', 'day', 'week', 'monthday' 等字段。
     * @return string 生成的cron表达式，如果周期类型不匹配则返回空字符串。
     * @throws Exception 如果传入的数据不符合要求，会抛出异常。
     */
    public function createRule(array $data): string
    {
        // 从数据中获取周期类型
        $cycle = $data['cycle'];
        // 根据周期类型进行不同的处理
        switch ($cycle) {
            case 1: // 每分钟执行
                // 检查分钟数值是否有效
                if (
                    !isset($data['minute'])
                    || !is_numeric($data['minute'])
                ) {
                    // 若无效，抛出异常
                    throw new Exception('分钟数值错误，请检查');
                }
                // 生成每分钟执行的cron表达式
                return '*/' . $data['minute'] . ' * * * *';
                break;
            case 2: // 每小时执行
                // 检查小时数值是否有效
                if (
                    !isset($data['hour'])
                    || !is_numeric($data['hour'])
                ) {
                    // 若无效，抛出异常
                    throw new Exception('小时数值错误，请检查');
                }
                // 生成每小时执行的cron表达式
                return  '0 */' . $data['hour'] . ' * * *';
                break;
            case 3: // 每天执行
                // 检查时间点和日期数值是否有效
                if (
                    !isset($data['time'])
                    || !isset($data['day'])
                    || !is_numeric($data['day'])

                ) {
                    // 若无效，抛出异常
                    throw new Exception('时间点错误，请检查');
                }
                // 生成每天执行的cron表达式
                return  date('i', strtotime($data['time'])) . ' ' . date('H', strtotime($data['time'])) . ' */' . $data['day'] . ' * *';
                // 示例：5 4 */2 * *
                break;
            case 4: // 每周执行
                // 检查时间点和星期数值是否有效
                if (
                    !isset($data['time'])
                    || !isset($data['week'])
                    || !is_numeric($data['week'])
                ) {
                    // 若无效，抛出异常
                    throw new Exception('时间点或星期错误，请检查');
                }
                // 生成每周执行的cron表达式
                return    date('i', strtotime($data['time'])) . ' ' . date('H', strtotime($data['time'])) . ' * * ' . $data['week'];
                break;
            case 5: // 每月执行
                // 检查时间点和日期数值是否有效
                if (
                    !isset($data['time'])
                    || !isset($data['monthday'])
                    || !is_numeric($data['monthday'])
                ) {
                    // 若无效，抛出异常
                    throw new Exception('时间点或日期错误，请检查');
                }
                // 生成每月执行的cron表达式
                return    date('i', strtotime($data['time'])) . ' ' . date('H', strtotime($data['time'])) . ' ' . $data['monthday'] . ' * *';
                // 示例：15 14 1 * * 在下午 02:15, 限每月 1 号 执行
                break;
        }
        // 如果周期类型不匹配，返回空字符串
        return '';
    }

    /**
     * 解锁任务文件
     * 当有任务新增或编辑的时候 删除lock文件 让系统重新生成计划任务列表
     * @return void   
     */
    public function unLockTaskFile()
    {
        FileHelper::delFile(runtime_path() . 'crontabtask.lock');
    }

    /**
     * 分页查询
     *
     * @param string $name
     * @param string $ident
     * @param integer $pagesize
     * @return LengthAwarePaginator
     */
    public function paginator(
        string $title = '',
        int $pagesize = 20
    ): LengthAwarePaginator {
        $taks =  Db::table('frame_crontab_task')
            ->when($title, function ($query) use ($title) {
                return $query->where('title', 'like', '%' . $title . '%');
            })
            ->orderBy('id', 'desc')
            ->paginate($pagesize);

        $bllComponentEvent       = new ComponentEvent();
        $bllComponentEventMethod = new ComponentEventMethod();
        foreach ($taks as $key => $task) {

            if ($task->tasktype == 1) {
                //查询事件信息
                list($event, $class, $method) = explode('/', $task->event);
                $eventinfo                          = $bllComponentEvent->findByIdent($event);
                $methodinfo                         = [];
                if ($eventinfo) {
                    $methodinfo = $bllComponentEventMethod->findByPath($class . '/' . $method, $eventinfo['id']);
                }
                if ($eventinfo && $methodinfo) {
                    //将事件信息和事件组信息合并到任务信息中
                    $taks[$key]->eventdesc = $eventinfo['title'] . '/' . $methodinfo['title'];
                }
            }
        }
        return $taks;
    }
}
